﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace BFlatCompile.Nodes
{
	public abstract class Expression : ICodeContainer
	{
		public static readonly Expression[] EMPTY_LIST = new Expression[0];

		public abstract void ResolveCode(Class c, Dictionary<object, object> parents);

		public static Expression Parse(Tokens tokens)
		{
			return ParseTernary(tokens);
		}

		private static Expression ParseTernary(Tokens tokens)
		{
			Expression expression = ParseBooleanCombinator(tokens);

			if (tokens.PopIfPresent("?"))
			{
				Token questionToken = tokens.PreviousToken();
				Expression trueExpression = ParseTernary(tokens);
				tokens.PopExpected(":");
				Expression falseExpression = ParseTernary(tokens);

				expression = Ternary.Create(expression, trueExpression, falseExpression, questionToken);
			}

			return expression;
		}

		private static readonly HashSet<string> VALID_BOOLEAN_COMBINATORS = new HashSet<string>("&& || ^^".Split(' '));

		private static Token PopBooleanCombinatorIfPresent(Tokens tokens)
		{
			return VALID_BOOLEAN_COMBINATORS.Contains(tokens.Peek())
				? tokens.PopToken()
				: null;
		}

		private static Expression ParseBooleanCombinator(Tokens tokens)
		{
			Expression expression = ParseEquality(tokens);
			Token combinatorToken = PopBooleanCombinatorIfPresent(tokens);
			if (combinatorToken != null)
			{
				List<Expression> expressions = new List<Expression>() { expression };
				// TODO: now that I've added token information to the Expression objects, these lists of strings for ops that mirror the token list that appear periodically are mostly unnecessary. Should remove.
				List<string> ops = new List<string>();
				List<Token> opTokens = new List<Token>();
				while (combinatorToken != null)
				{
					ops.Add(combinatorToken.Value);
					opTokens.Add(combinatorToken);
					expressions.Add(ParseEquality(tokens));
					combinatorToken = PopBooleanCombinatorIfPresent(tokens);
				}

				return new BooleanCombinator(expressions, ops, opTokens);
			}

			return expression;
		}

		private static readonly HashSet<string> VALID_COMPARISON_TOKENS = new HashSet<string>("== != <= >= < >".Split(' '));

		private static Token PopEqualityComparisonIfPresent(Tokens tokens)
		{
			return  VALID_COMPARISON_TOKENS.Contains(tokens.Peek())
				? tokens.PopToken()
				: null;
		}

		private static Expression ParseEquality(Tokens tokens)
		{
			Expression expression = ParseAddition(tokens);
			Token comparisonToken = PopEqualityComparisonIfPresent(tokens);

			if (comparisonToken != null)
			{
				List<Expression> expressions = new List<Expression>() { expression };
				List<string> ops = new List<string>();
				List<Token> opTokens = new List<Token>();
				while (comparisonToken != null)
				{
					ops.Add(comparisonToken.Value);
					expressions.Add(ParseAddition(tokens));
					opTokens.Add(comparisonToken);
					comparisonToken = PopEqualityComparisonIfPresent(tokens);
				}

				return new EqualityComparison(expressions, ops, opTokens);
			}

			return expression;
		}

		private static Expression ParseBitwise(Tokens tokens)
		{
			// TODO: this
			return null;
		}

		private static Expression ParseBitShift(Tokens tokens)
		{
			// TODO: this, and also determine where this goes in the order of operations
			return null;
		}

		private static Expression ParseAddition(Tokens tokens)
		{
			Expression expression = ParseMultiplication(tokens);

			string next = tokens.Peek();

			if (next == "+" || next == "-")
			{
				List<Expression> expressions = new List<Expression>() { expression };
				List<char> ops = new List<char>();
				List<Token> opTokens = new List<Token>();

				while (next == "+" || next == "-")
				{
					opTokens.Add(tokens.PopToken());
					ops.Add(next[0]);
					expressions.Add(Expression.ParseMultiplication(tokens));
					next = tokens.Peek();
				}

				return new AdditionChain(expressions, ops, opTokens);
			}

			return expression;
		}

		private static Expression ParseMultiplication(Tokens tokens)
		{
			Expression expression = ParseParenthesis(tokens);

			Token next = tokens.PeekToken();

			if (next.Value == "*" || next.Value == "/" || next.Value == "%")
			{
				List<Expression> expressions = new List<Expression>() { expression };
				List<char> ops = new List<char>();
				List<Token> opTokens = new List<Token>();

				while (next.Value == "*" || next.Value == "/" || next.Value == "%")
				{
					tokens.Pop();
					ops.Add(next.Value[0]);
					opTokens.Add(next);
					expressions.Add(Expression.ParseParenthesis(tokens));
					next = tokens.PeekToken();
				}

				return new MultiplicationChain(expressions, ops, opTokens);
			}
			return expression;
		}

		// TODO: bit shift, boolean combinator && ||, bitwise op, equality comparison

		private static Expression ParseParenthesis(Tokens tokens)
		{
			if (tokens.PopIfPresent("("))
			{
				Expression expression = ParseTernary(tokens);
				tokens.PopExpected(")");
				return expression;
			}

			return ParseUnary(tokens);
		}

		private static Expression ParseUnary(Tokens tokens)
		{
			if (tokens.PopIfPresent("!"))
			{
				Token bangToken = tokens.PreviousToken();
				Expression inner = ParseParenthesis(tokens);

				if (inner is BooleanConstant)
				{
					if (((BooleanConstant)inner).Value)
					{
						return BooleanConstant.False;
					}
					else
					{
						return BooleanConstant.True;
					}
				}
				return new BooleanNot(inner, bangToken);
			}

			if (tokens.PopIfPresent("-"))
			{
				Token negativeSignToken = tokens.PreviousToken();
				Expression inner = ParseParenthesis(tokens);

				int sign = -1;
				while (inner is NegativeSign)
				{
					inner = ((NegativeSign)inner).Expression;
					sign *= -1;
				}

				if (sign == -1)
				{
					return new NegativeSign(inner, negativeSignToken);
				}
				else
				{
					return inner;
				}
			}

			return ParseIncrement(tokens);
		}

		private static Expression ParseIncrement(Tokens tokens)
		{
			Expression inner;
			Increment outer = null;
			Token token = tokens.PeekToken();
			if (tokens.PopIfPresent("++"))
			{
				inner = ParseParenthesis(tokens);
				outer = new Increment(inner, 1, true, token);
			}
			else if (tokens.PopIfPresent("--"))
			{
				inner = ParseParenthesis(tokens);
				outer = new Increment(inner, -1, true, token);
			}
			else
			{
				inner = ParseListDefinition(tokens);
				token = tokens.PeekToken();
				if (tokens.PopIfPresent("++"))
				{
					outer = new Increment(inner, 1, false, token);
				}
				else if (tokens.PopIfPresent("--"))
				{
					outer = new Increment(inner, -1, false, token);
				}
			}

			if (outer == null)
			{
				return inner;
			}

			if ((inner is Variable) ||
				(inner is BracketIndex) ||
				(inner is DotDereference))
			{
				return outer;
			}

			throw new ParserException("Invalid usage of in-place " + (outer.Sign == 1 ? "increment" : "decrement") + ".", token);
		}

		private static Expression ParseListDefinition(Tokens tokens)
		{
			if (tokens.PopIfPresent("["))
			{
				List<Expression> items = new List<Expression>();
				while (tokens.Peek() != "]")
				{
					if (items.Count > 0)
					{
						string delete_me = tokens.Peek();
						tokens.PopExpected(",", "]");
					}

					string delete_me2 = tokens.Peek();

					items.Add(Expression.Parse(tokens));
				}

				tokens.PopExpected("]");

				return new ListDefinition(items.ToArray());
			}

			return ParseIdentifier(tokens);
		}

		private static Expression ParseIdentifier(Tokens tokens)
		{
			string token = tokens.Peek();
			Token tokenInstance = tokens.PeekToken();
			Token newKeywordToken = null;
			Expression expression;



			if (token == "new")
			{
				newKeywordToken = tokenInstance;
				tokens.Pop();
				tokenInstance = tokens.PeekToken();
			}

			if (token == "true")
			{
				tokens.Pop();
				expression = BooleanConstant.True;
				return ParseExpressionChain(tokens, expression, newKeywordToken);
			}

			if (token == "false")
			{
				tokens.Pop();
				expression = BooleanConstant.False;
				return ParseExpressionChain(tokens, expression, newKeywordToken);
			}

			if (token == "null")
			{
				tokens.Pop();
				expression = NullConstant.Instance;
				return ParseExpressionChain(tokens, expression, newKeywordToken);
			}

			if (token == "{")
			{
				expression = ParseMapDefinition(tokens);
				return ParseExpressionChain(tokens, expression, newKeywordToken);
			}

			if (token[0] == '\'' || token[0] == '"')
			{
				expression = ParseStringConstant(tokens);
				return ParseExpressionChain(tokens, expression, newKeywordToken);
			}

			if (token == "@")
			{
				expression = NativeCodeInsertion.ParseNativeCodeInsertion(tokens);
				return ParseExpressionChain(tokens, expression, newKeywordToken);
			}

			char c = token[0];

			if (c >= '0' && c <= '9')
			{
				expression = NumericalConstant.ParseNumber(tokens, false, false);
				return ParseExpressionChain(tokens, expression, newKeywordToken);
			}

			if ((c >= 'a' && c <= 'z') ||
				(c >= 'A' && c <= 'Z') ||
				(c == '_'))
			{
				string variableName = tokens.PopIdentifier();
				Token nameToken = tokens.PreviousToken();
				// TODO: move this special logic to the EffectiveExpression property, once created.
				expression = variableName == "this"
					? (Expression)(new ThisValue(nameToken))
					: new Variable(variableName, nameToken);

				return ParseExpressionChain(tokens, expression, newKeywordToken);
			}
			newKeywordToken = null; // unexpected token has dominance over unexpected new

			throw ParserException.UnexpectedToken(tokens, tokenInstance);
		}

		private static Expression ParseExpressionChain(Tokens tokens, Expression rootExpression, Token newKeywordToken)
		{
			bool newRequired = newKeywordToken != null;
			char c;
			string next = tokens.Peek();
			if (next == null) return rootExpression;

			c = next[0];

			bool chainOngoing = true;
			Token chainingToken = null;
			while (chainOngoing)
			{
				switch (c)
				{
					case '.':
						tokens.PopExpected(".");
						chainingToken = tokens.PreviousToken();
						string step = tokens.PopIdentifier();
						rootExpression = new DotDereference(rootExpression, step, chainingToken);
						break;

					case '[':
						tokens.PopExpected("[");
						chainingToken = tokens.PreviousToken();
						Expression inner = Parse(tokens);
						tokens.PopExpected("]");
						rootExpression = new BracketIndex(rootExpression, inner, chainingToken);
						break;

					case '(':
						tokens.PopExpected("(");
						chainingToken = tokens.PreviousToken();
						Expression[] paramList = ParseCommaList(tokens);
						tokens.PopExpected(")");
						rootExpression = new FunctionCall(rootExpression, paramList, newRequired, chainingToken);
						newRequired = false;
						break;

					default:
						chainOngoing = false;
						break;
				}
				Token tokenInstance = tokens.PeekToken();
				string token = tokenInstance.Value;

				if (token != null)
				{
					c = token[0];
				}
				else
				{
					if (newRequired)
					{
						throw new ParserException("Unexpected \"new\"", tokenInstance);
					}
					return rootExpression;
				}
			}

			if (newRequired)
			{
				throw new Exception("Unexpected \"new\"");
			}

			return rootExpression;
		}

		private static Expression ParseMapDefinition(Tokens tokens)
		{
			Token token = tokens.PeekToken();
			tokens.PopExpected("{");
			List<Expression> keys = new List<Expression>();
			List<Expression> values = new List<Expression>();
			List<Token> keyTokens = new List<Token>();

			bool first = true;
			while (true)
			{
				string t = tokens.Peek();
				if (t == "}" || t == null)
				{
					break;
				}

				if (!first)
				{
					tokens.PopExpected(",");
				}

				if (tokens.Peek() == "}")
				{
					break; // it's okay to have a comma after the last key-value pair.
				}
				keyTokens.Add(tokens.PeekToken());
				keys.Add(Expression.Parse(tokens));
				tokens.PopExpected(":");
				values.Add(Expression.Parse(tokens));

				first = false;
			}
			tokens.PopExpected("}");

			return new MapDefinition(keys, values, token, keyTokens);
		}

		private static Expression ParseStringConstant(Tokens tokens)
		{
			Token token = tokens.PeekToken();
			string value = tokens.Pop();
			value = value.Substring(1, value.Length - 2);
			return new StringConstant(value, token); // TODO: Remove? I don't think token info is necessary here.
		}

		public static Expression[] ParseCommaList(Tokens tokens)
		{
			List<Expression> expressions = new List<Expression>();
			bool first = true;
			while (tokens.Peek() != ")")
			{
				if (!first)
				{
					tokens.PopExpected(",", ")");
				}
				first = false;

				expressions.Add(Expression.Parse(tokens));
			}

			return expressions.ToArray();
		}

		public abstract void ResolveClassToThis(Class c);
	}
}
